/*
 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ohosvideodecoder.h"
#include <cstdint>

namespace ohos {
VideoDecoder::VideoDecoder(std::unique_ptr<CodecData> codecData, std::unique_ptr<VideoFormat> format)
{
    if (codecData != nullptr) {
        data_ = std::move(codecData);
    }

    if (format != nullptr) {
        format_ = std::move(format);
    }
}

VideoDecoder::~VideoDecoder()
{
    if (avFormat_ != nullptr) {
        OH_AVFormat_Destroy(avFormat_);
        avFormat_ = nullptr;
    }

    if (decoder_ != nullptr) {
        OH_VideoDecoder_Destroy(decoder_);
        decoder_ = nullptr;
    }
}

int32_t VideoDecoder::Create()
{
    if (decoder_) {
        return 0;
    }
    AVCODEC_CHECK(format_ == nullptr, "format is nullptr", -1);
    decoder_ = OH_VideoDecoder_CreateByMime(format_->codecMime.c_str());
    AVCODEC_CHECK(decoder_ == nullptr, "OH_VideoDecoder_CreateByName Failed", -1);

    return 0;
}

int VideoDecoder::SetCodecFormat()
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    AVCODEC_CHECK(format_ == nullptr, "format is NULL", -1);
    AVCODEC_CHECK(format_->width <= 0, "format width is invalid", -1);
    AVCODEC_CHECK(format_->height <= 0, "format height is invalid", -1);

    OH_AVFormat *avformat = OH_AVFormat_Create();
    AVCODEC_CHECK(avformat == nullptr, "OH_AVFormat_Create Failed", -1);

    OH_AVFormat_SetStringValue(avformat, OH_MD_KEY_CODEC_MIME, format_->codecMime.c_str());
    OH_AVFormat_SetIntValue(avformat, OH_MD_KEY_WIDTH, format_->width);
    OH_AVFormat_SetIntValue(avformat, OH_MD_KEY_HEIGHT, format_->height);
    OH_AVFormat_SetIntValue(avformat, OH_MD_KEY_PIXEL_FORMAT, format_->pixelFormat);

    int lowLatency = format_->lowLatency ? 1 : 0;
    OH_AVFormat_SetIntValue(avformat, OH_MD_KEY_VIDEO_ENABLE_LOW_LATENCY, lowLatency);

    int32_t ret = OH_VideoDecoder_Configure(decoder_, avformat);
    AVCODEC_CHECK(ret != AV_ERR_OK, "OH_VideoDecoder_Configure failed!", -1);
    OH_AVFormat_Destroy(avformat);
    avformat = nullptr;

    return ret;
}

CodecData *VideoDecoder::GetCodecData()
{
    AVCODEC_CHECK(data_ == nullptr, "codecData is NULL", nullptr);
    return data_.get();
}

int32_t VideoDecoder::Config()
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    AVCODEC_CHECK(data_.get() == nullptr, "codecData is NULL", -1);

    int32_t ret = SetCodecFormat();
    errorCode_ = (ret != AV_ERR_OK) ? ret : 0;
    AVCODEC_CHECK(ret != AV_ERR_OK, "SetCodecFormat failed!", -1);

    if (window_ != nullptr) {
        ret = OH_VideoDecoder_SetSurface(decoder_, window_);
        AVCODEC_CHECK(ret != AV_ERR_OK, "SetSurface failed!", -1);
    }

    ret = SetCallback();
    errorCode_ = (ret != AV_ERR_OK) ? ret : 0;
    AVCODEC_CHECK(ret != AV_ERR_OK, "SetCallback failed!", -1);

    ret = OH_VideoDecoder_Prepare(decoder_);
    errorCode_ = (ret != AV_ERR_OK) ? ret : 0;
    AVCODEC_CHECK(ret != AV_ERR_OK, "OH_VideoDecoder_Prepare failed!", -1);
    state_ = State::CONFIG;

    return ret;
}

int32_t VideoDecoder::Start()
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    AVCODEC_CHECK(state_ == State::RUNNING, "decoder has been started", -1);
    AVCODEC_CHECK((state_ == State::UNKNOWN || state_ == State::RELEASE), "decoder has not be ready", -1);

    // 启动编码器，开始编码
    int ret = OH_VideoDecoder_Start(decoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "VideoDecoder", "OH_VideoDecoder_Start Failed!");
        errorCode_ = ret;
        return -1;
    }
    state_ = State::RUNNING;
    return 0;
}

int32_t VideoDecoder::PushInputData(uint8_t *pktData, size_t dataSize, int64_t pts, uint32_t flag,
                                    std::chrono::milliseconds waitTimes)
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    AVCODEC_CHECK(data_.get() == nullptr, "codecdata is NULL", -1);
    AVCODEC_CHECK(state_ != State::RUNNING, "decoder is not running!", ERRCODE_DECODE_ERRSTAT);

    OH_AVCodecBufferAttr attr = {0, 0, 0, AVCODEC_BUFFER_FLAGS_NONE};
    attr.pts = pts;
    attr.size = dataSize;
    attr.flags = flag;
    CodecData *codecData = data_.get();
    int indx = -1;
    int ret = codecData->InputData(pktData, dataSize, &attr, &indx, waitTimes);
    if (ret != ERRCODE_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "VideoDecoder", "codec data set InputData failed,\
                     errcode=%{public}d", ret);
        return ret;
    }
    return FreeInputData(indx);
}

// 获取解码帧
int32_t VideoDecoder::GetOutputData(AvBufferInfo &info, std::chrono::milliseconds waitTimes)
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    AVCODEC_CHECK(data_ == nullptr, "codecdata is NULL", -1);
    AVCODEC_CHECK(state_ != State::RUNNING, "decoder is not running!", ERRCODE_DECODE_ERRSTAT);
    return data_->OutputData(info, waitTimes);
}

int32_t VideoDecoder::FreeOutputData(uint32_t index)
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    AVCODEC_CHECK(state_ != State::RUNNING, "decoder is not running!", ERRCODE_DECODE_ERRSTAT);

    int ret = OH_VideoDecoder_FreeOutputBuffer(decoder_, index);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "VideoDecoder", "OH_VideoDecoder_FreeOutputBuffer failed");
        errorCode_ = ret;
        return -1;
    }

    return 0;
}

int32_t VideoDecoder::Stop()
{
    state_ = VideoDecoder::State::STOP;
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    int32_t ret = OH_VideoDecoder_Flush(decoder_);
    AVCODEC_CHECK(ret != AV_ERR_OK, "OH_VideoDecoder_Flush Failed", -1);
    // 终止解码器videoDec
    ret = OH_VideoDecoder_Stop(decoder_);
    AVCODEC_CHECK(ret != AV_ERR_OK, "OH_VideoDecoder_Stop Failed", -1);

    return 0;
}

void VideoDecoder::Release()
{
    Stop();
    if (decoder_ != nullptr) {
        OH_VideoDecoder_Destroy(decoder_);
        decoder_ = nullptr;
    }
    state_ = VideoDecoder::State::RELEASE;
}

int32_t VideoDecoder::SetCallback()
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    OH_AVCodecCallback cb = { &DataCallback::OnCodecError, 
                              &DataCallback::OnCodecFormatChange, 
                              &DataCallback::OnNeedInputBuffer, 
                              &DataCallback::OnNewOutputBuffer };

    int32_t ret = OH_VideoDecoder_RegisterCallback(decoder_, cb, this);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "VideoDecoder", "OH_VideoDecoder_RegisterCallback Failed");
        errorCode_ = ret;
        ret = ERRCODE_UNKNOWN;
    }
    return ret;
}

int32_t VideoDecoder::FreeInputData(int32_t index)
{
    AVCODEC_CHECK(decoder_ == nullptr, "decoder is NULL", -1);
    int32_t ret = OH_VideoDecoder_PushInputBuffer(decoder_, index);
    if (ret != AV_ERR_OK) {
        errorCode_ = ret;
        return ERRCODE_UNKNOWN;
    }
    return ERRCODE_OK;
}

int32_t VideoDecoder::GetFormatInfo(FormatType type)
{
    int result = 0;

    const char * const formatKeys[FORMAT_TYPE_NBR] = {
        OH_MD_KEY_VIDEO_PIC_WIDTH,
        OH_MD_KEY_VIDEO_PIC_HEIGHT,
        OH_MD_KEY_VIDEO_STRIDE,
        OH_MD_KEY_VIDEO_SLICE_HEIGHT,
        OH_MD_KEY_PIXEL_FORMAT,
        OH_MD_KEY_VIDEO_CROP_TOP,
        OH_MD_KEY_VIDEO_CROP_BOTTOM,
        OH_MD_KEY_VIDEO_CROP_LEFT,
        OH_MD_KEY_VIDEO_CROP_RIGHT,
        OH_MD_KEY_WIDTH,
        OH_MD_KEY_HEIGHT,
    };

    AVCODEC_CHECK (avFormat_ == nullptr, "avFormat_ is not initted!", ERRCODE_UNKNOWN);
    AVCODEC_CHECK ((type < 0 || type >= FORMAT_TYPE_NBR), "FormatType ERROR!", ERRCODE_PARAM_INVALID);

    bool ret = OH_AVFormat_GetIntValue(avFormat_, formatKeys[type], &result);
    
    return ret ? result : -1;
}

void VideoDecoder::DataCallback::OnCodecError(OH_AVCodec *codec, int32_t errorCode,
                                              void *userData)
{
    (void)codec;
    (void)errorCode;
    VideoDecoder *decoder = static_cast<VideoDecoder *>(userData);
    decoder->errorCode_ = errorCode;
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "Decoder", "On decoder error, error code: %{public}d", errorCode);
}

void VideoDecoder::DataCallback::OnCodecFormatChange(OH_AVCodec *codec, OH_AVFormat *format,
                                                     void *userData)
{
    (void)codec;
    if (userData == nullptr || format == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "Decoder", "OnCodecFormatChange error, userData/format is NULL");
        return;
    }

    VideoDecoder *decoder = static_cast<VideoDecoder *>(userData);
    if (decoder->avFormat_ == nullptr) {
        decoder->avFormat_ = OH_AVFormat_Create();
    }
    OH_AVFormat_Copy(decoder->avFormat_, format);

    if (decoder->fmtChanged_ != nullptr) {
        *decoder->fmtChanged_ = 1;
    }
}

void VideoDecoder::DataCallback::OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index,
                                                   OH_AVBuffer *buffer, void *userData)
{
 
    (void)codec;
    if (userData == nullptr || buffer == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "Decoder", "OnNeedInputBuffer error, userData is NULL");
        return;
    }
    VideoDecoder *decoder = static_cast<VideoDecoder *>(userData);
    CodecData *codecData = decoder->GetCodecData();
    if (codecData != nullptr) {
        codecData->NeedInputBuffer(index, buffer);
    }
}

void VideoDecoder::DataCallback::OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index,
                                                   OH_AVBuffer *buffer, void *userData)
{
    (void)codec;
    if (userData == nullptr || buffer == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "Decoder", "OnNewOutputBuffer error, userData/OH_AVBuffer is NULL");
        return;
    }
    VideoDecoder *decoder = static_cast<VideoDecoder *>(userData);
    CodecData *codecData = decoder->GetCodecData();
    if (codecData != nullptr) {
        codecData->NewOutputBuffer(index, buffer);
    }
}

size_t VideoDecoder::GetInputBufferSize()
{
    AVCODEC_CHECK(decoder_ == nullptr, "encoder is NULL", -1);
    AVCODEC_CHECK(data_ == nullptr, "codecdata is NULL", -1);
    AVCODEC_CHECK(state_ != State::RUNNING, "encoder is not running!", ERRCODE_DECODE_ERRSTAT);

    AvBufferInfo info {0, nullptr};
    int ret = data_->GetInputBuffer(info, std::chrono::milliseconds(8));
    if (ret == ohos::ERRCODE_OK) {
        return info.AvBufferSize();
    }
    return 0;
}
} // namespace ohos
